package com.ds.lens.manager.scheduler;

import com.ds.lens.data.common.util.HttpUtil;
import com.ds.lens.manager.bo.TaskBO;
import lombok.Getter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import javax.annotation.PreDestroy;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * Description:
 *
 * @author WeiShaoying
 * @date 2020-04-23
 */
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
    @Getter
    private volatile ScheduledTaskRegistrar scheduledTaskRegistrar;

    private static final String FORMAT = "0 mm HH dd MM ?";

    private final ConcurrentHashMap<String, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();

    private final ConcurrentHashMap<String, CronTask> cronTasks = new ConcurrentHashMap<>();


    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        this.scheduledTaskRegistrar = scheduledTaskRegistrar;
    }

    public void refreshTasks(Map<String, TaskBO> taskBOMap) {
        removeUselessTask(taskBOMap);
        taskBOMap.forEach((key, taskBO) -> {
            if (taskBO.getExecutionTime().before(new Date())) {
                request(taskBO.getBackUrl(), taskBO.getMethod(), taskBO.getParams());
                return;
            }
            addTask(key, taskBO);
        });
    }

    private void addTask(String key, TaskBO taskBO) {
        String expression = getExpression(taskBO.getExecutionTime());
        if (!cronTasks.containsKey(key)) {
            doAddTask(key, taskBO, expression);
        } else if (!cronTasks.get(key).getExpression().equals(expression)) {
            cancelTask(key);
            doAddTask(key, taskBO, expression);
        }
    }

    private void removeUselessTask(Map<String, TaskBO> taskBOMap) {
        Set<String> keys = scheduledFutures.keySet();
        keys.forEach(key -> {
            if (taskBOMap.get(key) == null) {
                scheduledFutures.get(key).cancel(false);
                scheduledFutures.remove(key);
                cronTasks.remove(key);
            }
        });
    }

    private void doAddTask(String key, TaskBO taskBO, String expression) {
        CronTask task = new CronTask(new ScheduleTask(taskBO.getParams(), taskBO.getBackUrl(), taskBO.getMethod()), expression);
        ScheduledFuture<?> future = scheduledTaskRegistrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
        cronTasks.put(key, task);
        scheduledFutures.put(key, future);
    }

    private void cancelTask(String key) {
        scheduledFutures.get(key).cancel(false);
        scheduledFutures.remove(key);
        cronTasks.remove(key);
    }

    private String getExpression(Date date) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT);
        return dateFormat.format(date);
    }

    @PreDestroy
    public void destroy() {
        this.scheduledTaskRegistrar.destroy();
    }

    private class ScheduleTask implements Runnable {
        private Map<String, Object> params;
        private String backUrl;
        private HttpMethod method;

        ScheduleTask(Map<String, Object> params, String backUrl, HttpMethod method) {
            this.params = params;
            this.backUrl = backUrl;
            this.method = method;
        }

        @Override
        public void run() {
            //左移几位相当于乘以2的几次方
            for (int i = 2; i <= 8; i = i << 1) {
                try {
                    if (request(backUrl, method, params)) {
                        return;
                    }
                    Thread.sleep(i * 60000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private boolean request(String backUrl, HttpMethod method, Map<String, Object> params) {
        ResponseEntity responseEntity;
        if (HttpMethod.GET.equals(method)) {
            responseEntity = HttpUtil.get(backUrl, params, null, 2000);
        } else {
            responseEntity = HttpUtil.post(backUrl, params, 2000);
        }
        return responseEntity.getStatusCode().value() == 200;
    }
}
